package com.mybatis.boost.generator.plugin;

import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.mybatis.boost.generator.plugin.adapter.MapperPluginAdapter;
import jodd.util.StringUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.TopLevelClass;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.google.common.collect.ImmutableMap.of;
import static com.mybatis.boost.generator.util.GeneratorUtil.addOrUpdateAnnotation;
import static com.mybatis.boost.mapper.model.MapperConstants.GENERATED_UNIQUE_ID;

/**
 * 全局列字段重写插件
 *
 * @author jearton
 * @since 2016/12/7
 */
@Slf4j
public class ColumnOverridePlugin extends MapperPluginAdapter {

    private List<ColumnOverride> columnOverrides = Lists.newArrayList();

    private FullyQualifiedJavaType generatedValueType = new FullyQualifiedJavaType(GeneratedValue.class.getName());

    private FullyQualifiedJavaType columnType = new FullyQualifiedJavaType(Column.class.getName());

    @Override
    public boolean validate(List<String> warnings) {
        properties.forEach((k, v) -> {
            if (k == null || v == null) return;
            try {
                ColumnOverride columnOverride = new ColumnOverride();
                ColumnOverrideSource columnOverrideSource = new ColumnOverrideSource();
                Map<String, String> keyMap = Splitter.on(",").withKeyValueSeparator("=").split(k.toString());
                String column = keyMap.get("column");
                if (column != null) {
                    List<String> fullColumn = Splitter.on(".").splitToList(column);
                    if (fullColumn.size() == 1) {
                        columnOverrideSource.setColumn(fullColumn.get(0));
                    } else {
                        columnOverrideSource.setTable(fullColumn.get(0));
                        columnOverrideSource.setColumn(fullColumn.get(1));
                    }
                }
                columnOverrideSource.setJdbcType(keyMap.get("jdbcType"));
                columnOverride.setSource(columnOverrideSource);

                Map<String, String> valueMap = Splitter.on(",").withKeyValueSeparator("=").split(v.toString());
                columnOverride.setJavaProperty(valueMap.get("javaProperty"));
                columnOverride.setJavaType(valueMap.get("javaType"));
                columnOverride.setInsertable(Boolean.valueOf(valueMap.getOrDefault("insertable", "true")));
                columnOverride.setUpdatable(Boolean.valueOf(valueMap.getOrDefault("updatable", "true")));
                columnOverride.setAutoUniqueId(Boolean.valueOf(valueMap.getOrDefault("autoUniqueId", "false")));
                columnOverrides.add(columnOverride);
            } catch (IllegalArgumentException e) {
                log.error("column override properties error format", e);
            }
        });
        return columnOverrides.size() > 0;
    }

    @Override
    public boolean modelFieldGenerated(Field field, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
        String table = introspectedTable.getFullyQualifiedTable().getIntrospectedTableName();
        String column = introspectedColumn.getActualColumnName();
        String jdbcType = introspectedColumn.getJdbcTypeName();

        // 匹配column
        columnOverrides.stream()
                .filter(columnOverride -> columnOverride.getSource().matchColumn(column))
                .findFirst()
                .ifPresent(columnOverride -> _executeOverride(columnOverride, field, topLevelClass, introspectedColumn));

        // 匹配jdbcType
        columnOverrides.stream()
                .filter(columnOverride -> columnOverride.getSource().matchType(jdbcType))
                .findFirst()
                .ifPresent(columnOverride -> _executeOverride(columnOverride, field, topLevelClass, introspectedColumn));

        // 匹配table+column
        columnOverrides.stream()
                .filter(columnOverride -> columnOverride.getSource().matchColumn(table, column))
                .findFirst()
                .ifPresent(columnOverride -> _executeOverride(columnOverride, field, topLevelClass, introspectedColumn));
        return true;
    }

    private void _executeOverride(ColumnOverride columnOverride, Field field, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn) {
        // 重写java属性名
        String javaProperty = columnOverride.getJavaProperty();
        if (StringUtil.isNotBlank(javaProperty) && !Objects.equals(introspectedColumn.getActualColumnName(), javaProperty)) {
            _addPersistenceType(topLevelClass, columnType);
            introspectedColumn.setJavaProperty(javaProperty);
            field.setName(javaProperty);
        }

        // 重写java类型
        String javaType = columnOverride.getJavaType();
        if (StringUtil.isNotBlank(javaType)) {
            FullyQualifiedJavaType type = new FullyQualifiedJavaType(javaType);
            _addPersistenceType(topLevelClass, type);
            introspectedColumn.setFullyQualifiedJavaType(type);
            field.setType(type);
        }

        // 非插入字段
        if (!columnOverride.isInsertable()) {
            _addPersistenceType(topLevelClass, columnType);
            addOrUpdateAnnotation(field, "@Column", of("insertable", "false"));
        }

        // 非更新字段
        if (!columnOverride.isUpdatable()) {
            _addPersistenceType(topLevelClass, columnType);
            addOrUpdateAnnotation(field, "@Column", of("updatable", "false"));
        }

        if (!columnOverride.isInsertable() && !columnOverride.isUpdatable()) {
            introspectedColumn.setGeneratedAlways(true);
        }

        // uuid字段
        if (columnOverride.isAutoUniqueId()) {
            _addPersistenceType(topLevelClass, generatedValueType);
            addOrUpdateAnnotation(field, "@GeneratedValue", of("generator", "\"" + GENERATED_UNIQUE_ID + "\""));
        }
    }

    private static void _addPersistenceType(TopLevelClass topLevelClass, FullyQualifiedJavaType javaType) {
        if (topLevelClass.getImportedTypes().stream()
                .anyMatch(type -> Objects.equals(type.getFullyQualifiedName(), "javax.persistence.*"))) {
            return;
        }
        topLevelClass.addImportedType(javaType);
    }

    @Data
    private static class ColumnOverride {
        // 列重写依据
        private ColumnOverrideSource source;

        // 类字段名
        private String javaProperty;
        // 类字段类型
        private String javaType;
        //whether the column is included in SQL INSERT statements
        private boolean insertable;
        //whether the column is included in SQL UPDATE statements
        private boolean updatable;
        //whether the column is auto generated with custom unique id
        private boolean autoUniqueId;
    }

    @Data
    private static class ColumnOverrideSource {
        // 数据库表名
        private String table;
        // 数据库列名
        private String column;
        // 数据库列类型
        private String jdbcType;

        boolean matchColumn(String table, String column) {
            return table != null && this.table != null && table.matches(this.table) && Objects.equals(this.column, column);
        }

        boolean matchColumn(String column) {
            return this.table == null && Objects.equals(this.column, column);
        }

        boolean matchType(String jdbcType) {
            return Objects.equals(this.jdbcType, jdbcType);
        }
    }
}
